/****************************************************************************
 * Copyright (c) 1998 Free Software Foundation, Inc.                        *
 *                                                                          *
 * Permission is hereby granted, free of charge, to any person obtaining a  *
 * copy of this software and associated documentation files (the            *
 * "Software"), to deal in the Software without restriction, including      *
 * without limitation the rights to use, copy, modify, merge, publish,      *
 * distribute, distribute with modifications, sublicense, and/or sell       *
 * copies of the Software, and to permit persons to whom the Software is    *
 * furnished to do so, subject to the following conditions:                 *
 *                                                                          *
 * The above copyright notice and this permission notice shall be included  *
 * in all copies or substantial portions of the Software.                   *
 *                                                                          *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
 *                                                                          *
 * Except as contained in this notice, the name(s) of the above copyright   *
 * holders shall not be used in advertising or otherwise to promote the     *
 * sale, use or other dealings in this Software without prior written       *
 * authorization.                                                           *
 ****************************************************************************/

/****************************************************************************
 *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
 *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
 ****************************************************************************/

/*
**	lib_getch.c
**
**	The routine getch().
**
*/

#include <curses.priv.h>

MODULE_ID("$Id: lib_getch.c 153052 2008-08-13 01:17:50Z coreos $")

#include <fifo_defs.h>

#if	defined(__QNX__) && !defined(__QNXNTO__)
#include <sys/dev.h>
#endif

int ESCDELAY = 1000;	/* max interval betw. chars in funkeys, in millisecs */

static inline int fifo_peek(void)
{
	int ch = SP->_fifo[peek];
	T(("peeking at %d", peek));
	
	p_inc();
	return ch;
}


static inline int fifo_pull(void)
{
int ch;
	ch = SP->_fifo[head];
	T(("pulling %d from %d", ch, head));

	if (peek == head)
	{
	    h_inc();
	    peek = head;
	}
	else
	    h_inc();
	    
#ifdef TRACE
	if (_nc_tracing & TRACE_IEVENT) _nc_fifo_dump();
#endif
	return ch;
}

static inline int fifo_push(void)
{
int n = 0;
unsigned int ch;

	if (tail == -1) return ERR;

#ifdef HIDE_EINTR
again:
	errno = 0;
#endif

#if USE_GPM_SUPPORT	
	if ((SP->_mouse_fd >= 0) 
	 && (_nc_timed_wait(3, -1, (int *)0) & 2))
	{
		SP->_mouse_event(SP);
		ch = KEY_MOUSE;
		n = 1;
	} else
#elif	defined(__QNX__) && !defined(__QNXNTO__)
	{
		int res = 0;

		if ((SP->_qnx_mouse_ctrl != 0) || (SP->_qnx_user_callback != 0))
			while (res == 0)
				res = _nc_timed_wait(3, -1, (int *)0);

		if ((SP->_qnx_mouse_ctrl != 0) && (res & 2)) 
		{
			SP->_mouse_event(SP);
			ch = KEY_MOUSE;
			n = 1;
		} else	{
			unsigned char c2 = 0;

			n = dev_read(SP->_ifd, &c2, 1, 1, 0, 0, 0, 0);
			ch = c2;
		}
	}
#else
	{
		unsigned char c2=0;
		n = read(SP->_ifd, &c2, 1);
		ch = c2;
	}
#endif

#ifdef HIDE_EINTR
	/*
	 * Under System V curses with non-restarting signals, getch() returns
	 * with value ERR when a handled signal keeps it from completing.  
	 * If signals restart system calls, OTOH, the signal is invisible
	 * except to its handler.
	 * 
	 * We don't want this difference to show.  This piece of code
	 * tries to make it look like we always have restarting signals.
	 */
	if (n <= 0 && errno == EINTR)
		goto again;
#endif

	if ((n == -1) || (n == 0))
	{
	    T(("read(%d,&ch,1)=%d, errno=%d", SP->_ifd, n, errno));
	    return ERR;
	}
	T(("read %d characters", n));

	SP->_fifo[tail] = ch;
	SP->_fifohold = 0;
	if (head == -1)
	    head = peek = tail;
	t_inc();
	T(("pushed %#x at %d", ch, tail));
#ifdef TRACE
	if (_nc_tracing & TRACE_IEVENT) _nc_fifo_dump();
#endif
	return ch;
}

static inline void fifo_clear(void)
{
int i;
	for (i = 0; i < FIFO_SIZE; i++)
		SP->_fifo[i] = 0;
	head = -1; tail = peek = 0;
}

static int kgetch(WINDOW *);

#define wgetch_should_refresh(win) (\
	(is_wintouched(win) || (win->_flags & _HASMOVED)) \
	&& !(win->_flags & _ISPAD))

int
wgetch(WINDOW *win)
{
int	ch;

	T((T_CALLED("wgetch(%p)"), win));

	if (!win)
	  returnCode(ERR);

	if (cooked_key_in_fifo())
	{
		if (wgetch_should_refresh(win))
			wrefresh(win);

		ch = fifo_pull();
		T(("wgetch returning (pre-cooked): %#x = %s", ch, _trace_key(ch));)
		returnCode(ch);
	}

	/*
	 * Handle cooked mode.  Grab a string from the screen,
	 * stuff its contents in the FIFO queue, and pop off
	 * the first character to return it.
	 */
	if (head == -1 && !SP->_raw && !SP->_cbreak)
	{
		char	buf[MAXCOLUMNS], *sp;

		T(("filling queue in cooked mode"));

		wgetnstr(win, buf, MAXCOLUMNS);

		/* ungetch in reverse order */
		ungetch('\n');
		for (sp = buf+strlen(buf); sp>buf; sp--)
			ungetch(sp[-1]);

		returnCode(fifo_pull());
	}

	if (wgetch_should_refresh(win))
		wrefresh(win);

	if (!win->_notimeout && (win->_delay >= 0 || SP->_cbreak > 1)) {
	int delay;

		T(("timed delay in wgetch()"));
		if (SP->_cbreak > 1)
		    delay = (SP->_cbreak - 1) * 100;
		else
		    delay = win->_delay;

		T(("delay is %d milliseconds", delay));

		if (head == -1)	/* fifo is empty */
			if (!_nc_timed_wait(3, delay, (int *)0))
				returnCode(ERR);
		/* else go on to read data available */
	}

	if (win->_use_keypad) {
		/*
		 * This is tricky.  We only want to get special-key
		 * events one at a time.  But we want to accumulate
		 * mouse events until either (a) the mouse logic tells
		 * us it's picked up a complete gesture, or (b)
		 * there's a detectable time lapse after one.
		 *
		 * Note: if the mouse code starts failing to compose
		 * press/release events into clicks, you should probably
		 * increase the wait with mouseinterval().
		 */
#if	defined(__QNX__) && !defined(__QNXNTO__)
		int runcount;

		do {
			for (runcount = 0; runcount < 8;) 
			{
				ch = kgetch(win);
				if (ch != KEY_MOUSE)
					break;
				++runcount;
				if (!SP->_mouse_inline(SP) && !SP->_qnx_mouse_ctrl)
					break;
				if (_nc_timed_wait(3, SP->_maxclick, (int *)0) <= 0)
					break;
			}
			if (!SP->_mouse_parse(runcount))
				runcount = 0;
		} while (ch == KEY_MOUSE && runcount == 0);
#else
		int runcount = 0;

		do {
			ch = kgetch(win);
			if (ch == KEY_MOUSE)
			{
				++runcount;
				if (!SP->_mouse_inline(SP))
				    break;
			}
		} while ((ch == KEY_MOUSE && (_nc_timed_wait(3, SP->_maxclick, (int *)0) > 0))
			  || (runcount && !SP->_mouse_parse(runcount)) );
#endif

		if (runcount > 0 && ch != KEY_MOUSE)
		{
		    /* mouse event sequence ended by keystroke, push it */
		    ungetch(ch);
		    ch = KEY_MOUSE;
		}
	} else {
		if (head == -1)
			fifo_push();
		ch = fifo_pull();
	}

	if (ch == ERR)
	{
#if USE_SIZECHANGE
	    if(SP->_sig_winch)
	    {
		_nc_update_screensize();
		/* resizeterm can push KEY_RESIZE */
		if(cooked_key_in_fifo())
		{
		    ch = fifo_pull();
		    T(("wgetch returning (pre-cooked): %#x = %s", ch, _trace_key(ch));)
		    returnCode(ch);
		}
	    }
#endif
	    T(("wgetch returning ERR"));
	    returnCode(ERR);
	}

	/*
	 * Simulate ICRNL mode
	 */
	if ((ch == '\r') && SP->_nl)
		ch = '\n';

	/* Strip 8th-bit if so desired.  We do this only for characters that
	 * are in the range 128-255, to provide compatibility with terminals
	 * that display only 7-bit characters.  Note that 'ch' may be a
	 * function key at this point, so we mustn't strip _those_.
	 */
	if ((ch < KEY_MIN) && (ch & 0x80))
		if (!SP->_use_meta)
			ch &= 0x7f;

	if (SP->_echo && ch < KEY_MIN && !(win->_flags & _ISPAD))
		wechochar(win, (chtype)ch);

	T(("wgetch returning : %#x = %s", ch, _trace_key(ch)));

	returnCode(ch);
}


/*
**      int
**      kgetch()
**
**      Get an input character, but take care of keypad sequences, returning
**      an appropriate code when one matches the input.  After each character
**      is received, set an alarm call based on ESCDELAY.  If no more of the
**      sequence is received by the time the alarm goes off, pass through
**      the sequence gotten so far.
**
**	This function must be called when there is no cooked keys in queue.
**	(that is head==-1 || peek==head)
**
*/

static int
kgetch(WINDOW *win GCC_UNUSED)
{
struct tries  *ptr;
int ch = 0;
int timeleft = ESCDELAY;

	TR(TRACE_IEVENT, ("kgetch(%p) called", win));

	ptr = SP->_keytry;

	for(;;)
	{
		if (!raw_key_in_fifo())
		{
		    if(fifo_push() == ERR)
		    {
			peek = head;	/* the keys stay uninterpreted */
			return ERR;
		    }
		}
		ch = fifo_peek();
		if (ch >= KEY_MIN)
		{
		    peek = head;
		    /* assume the key is the last in fifo */
		    t_dec(); /* remove the key */
		    return ch;
		}

		TR(TRACE_IEVENT, ("ch: %s", _trace_key(ch)));
		while ((ptr != NULL) && (ptr->ch != (unsigned char)ch))
			ptr = ptr->sibling;
#ifdef TRACE
		if (ptr == NULL)
			{TR(TRACE_IEVENT, ("ptr is null"));}
		else
			TR(TRACE_IEVENT, ("ptr=%p, ch=%d, value=%d",
					ptr, ptr->ch, ptr->value));
#endif /* TRACE */

		if (ptr == NULL)
			break;

		if (ptr->value != 0) {	/* sequence terminated */
			TR(TRACE_IEVENT, ("end of sequence"));
			if (peek == tail)
			    fifo_clear();
			else
			    head = peek;
#if	defined(__QNX__) && !defined(__QNXNTO__)
			if (ptr->value == KEY_MOUSE_SUB1 ||
				ptr->value == KEY_MOUSE_SUB2 ||
				ptr->value == KEY_MOUSE_SUB3)
			{
				SP->_qnx_term_mouse_subkey = ptr->value;
				return (KEY_MOUSE);
			} else
#endif
			return(ptr->value);
		}

		ptr = ptr->child;

		if (!raw_key_in_fifo())
		{
			TR(TRACE_IEVENT, ("waiting for rest of sequence"));
			if (!_nc_timed_wait(3, timeleft, &timeleft)) {
				TR(TRACE_IEVENT, ("ran out of time"));
				break;
			}
		}
	}
	ch = fifo_pull();
	peek = head;
	return ch;
}
